package de.dnb.gnd.utils.formatter;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import de.dnb.basics.applicationComponents.Pair;
import de.dnb.basics.applicationComponents.RangeCheckUtils;
import de.dnb.basics.filtering.FilterUtils;
import de.dnb.basics.filtering.IFunction;
import de.dnb.basics.filtering.IPredicate;
import de.dnb.gnd.exceptions.IllFormattedLineException;
import de.dnb.gnd.parser.Indicator;
import de.dnb.gnd.parser.Record;
import de.dnb.gnd.parser.Subfield;
import de.dnb.gnd.parser.line.Line;
import de.dnb.gnd.parser.tag.GNDTagDB;
import de.dnb.gnd.parser.tag.Tag;
import de.dnb.gnd.parser.tag.TagDB;
import de.dnb.gnd.utils.GNDUtils;
import de.dnb.gnd.utils.IFSubfieldToContent;
import de.dnb.gnd.utils.RecordUtils;
import de.dnb.gnd.utils.WorkUtils;

public class RDAFormatter {

    private static final boolean REMOVE_BRACKETS = false;

    protected Record actualRecord;

    protected TagDB tagDB;

    public final Pair<String, String> SLASH = new Pair<String, String>(" / ",
        "");

    public final Pair<String, String> KOMMA =
        new Pair<String, String>(", ", "");

    public final Pair<String, String> DOT = new Pair<String, String>(". ", "");

    public final Pair<String, String> BLANK = new Pair<String, String>(" ", "");

    public final Pair<String, String> NOTHING =
        new Pair<String, String>("", "");

    public final Pair<String, String> KOMMENT = new Pair<String, String>("* ",
        "");

    public final Pair<String, String> BRACKETS = new Pair<String, String>(" (",
        ")");

    public final Pair<String, String> CA = new Pair<String, String>("ca. ", "");

    public void setRecord(Record record) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        actualRecord = record;
        tagDB = record.tagDB;
    }

    public RDAFormatter(Record record) {
        setRecord(record);
    }

    /**
     * 
     * @param indicator nicht null
     * @return			null, wenn Standardverhalten
     */
    public final Pair<String, String> getPreAndPost(final Indicator indicator) {
        if (indicator == GNDTagDB.INDICATOR_100_A)
            return NOTHING;
        if (indicator == GNDTagDB.INDICATOR_100_C)
            return BLANK;
        if (indicator == GNDTagDB.INDICATOR_100_D)
            return KOMMA;
        if (indicator == GNDTagDB.INDICATOR_100_L)
            return KOMMA;
        if (indicator == GNDTagDB.INDICATOR_100_N)
            return BLANK;
        if (indicator == GNDTagDB.INDICATOR_100_P)
            return NOTHING;

        if (indicator == GNDTagDB.INDICATOR_110_A)
            return NOTHING;
        if (indicator == GNDTagDB.INDICATOR_110_B)
            return DOT;
        if (indicator == GNDTagDB.INDICATOR_110_N)
            return BRACKETS;

        if (indicator == GNDTagDB.INDICATOR_111_A)
            return NOTHING;
        if (indicator == GNDTagDB.INDICATOR_111_B)
            return SLASH;
        if (indicator == GNDTagDB.INDICATOR_111_C)
            return BLANK;

        if (indicator == GNDTagDB.INDICATOR_130_A)
            return NOTHING;
        if (indicator == GNDTagDB.INDICATOR_130_F)
            return BRACKETS;

        if (indicator == GNDTagDB.INDICATOR_130_M)
            return KOMMA;
        if (indicator == GNDTagDB.INDICATOR_130_N)
            return KOMMA;
        if (indicator == GNDTagDB.INDICATOR_130_O)
            return KOMMA;
        if (indicator == GNDTagDB.INDICATOR_130_P)
            return DOT;
        if (indicator == GNDTagDB.INDICATOR_130_R)
            return KOMMA;
        if (indicator == GNDTagDB.INDICATOR_130_S)
            return KOMMA;

        if (indicator == GNDTagDB.INDICATOR_150_A)
            return NOTHING;

        if (indicator == GNDTagDB.INDICATOR_151_A)
            return NOTHING;

        if (indicator == GNDTagDB.INDICATOR_548_C)
            return NOTHING;
        if (indicator == GNDTagDB.INDICATOR_548_D)
            return CA;

        // Vorsicht bei 151:
        if (indicator == GNDTagDB.DOLLAR_G)
            return BRACKETS;
        if (indicator == GNDTagDB.DOLLAR_X)
            return SLASH;
        if (indicator == GNDTagDB.DOLLAR_V_NR)
            return KOMMENT;
        if (indicator == GNDTagDB.DOLLAR_V_R)
            return KOMMENT;

        return null;
    }

    /**
     * 
     * @param line          nicht null
     * @return              RDA-Format der Zeile mit Deskriptionszeichen
     * @throws IllFormattedLineException
     *                      wenn Zeile oder verlinkter Datensatz nicht korrekt
     */
    public final String format(final Line line)
        throws IllFormattedLineException {
        RangeCheckUtils.assertReferenceParamNotNull("line", line);

        Tag tag = line.getTag();
        if (tag == GNDTagDB.TAG_100 || tag == GNDTagDB.TAG_400)
            return format100(line);
        if (tag == GNDTagDB.TAG_111 || tag == GNDTagDB.TAG_411)
            return format111(line);
        if (tag == GNDTagDB.TAG_130 || tag == GNDTagDB.TAG_430)
            return format130(line);
        if (tag == GNDTagDB.TAG_151 || tag == GNDTagDB.TAG_451)
            return format151(line);
        if (tag == GNDTagDB.TAG_548)
            return format548(line);

        if (tag == GNDTagDB.TAG_110 || tag == GNDTagDB.TAG_410
            || tag == GNDTagDB.TAG_150 || tag == GNDTagDB.TAG_450) {
            return formatSubfields(line.getSubfields());
        }

        return RecordUtils.toPicaWithoutTag(line);
    }

    /**
     * @param line
     * @return
     */
    public String exceptionHandling(
        Line line,
        Indicator... exceptionalIndicators) {
        Set<Indicator> excIndSet =
            new HashSet<Indicator>(Arrays.asList(exceptionalIndicators));
        boolean inBracket = false;
        List<Subfield> subs = line.getSubfields();
        String s = "";
        for (Subfield subfield : subs) {
            Indicator indicator = subfield.getIndicator();
            if (excIndSet.contains(indicator)) {
                if (inBracket == false)
                    s += " (";
                else
                    s += " : ";
                inBracket = true;
                String content = getContentWithoutBrackets(subfield);
                s += content;
            } else {
                if (inBracket == true)
                    s += ")";
                inBracket = false;
                s += formatStandard(subfield);
            }
        }
        if (inBracket == true)
            s += ")";
        return s;
    }

    private String format130(Line line) throws IllFormattedLineException {
        String s = formatSubfields(line.getSubfields());
        String idn = WorkUtils.getAuthorID(actualRecord);
        String p = "";
        if (idn != null) {

            Record personRecord = RecordUtils.readFromPortal(idn);
            if (personRecord != null) {
                //                RDAFormatter formatter = new RDAFormatter(personRecord);
                //                Line personLine = GNDUtils.getHeading(personRecord);
                //                p = formatter.format(personLine) + ". ";
                p = RDAFormatter.getRDAHeading(personRecord) + ". ";
            }
        }
        return p + s;
    }

    private String format111(Line line) {
        return exceptionHandling(line, GNDTagDB.INDICATOR_111_N,
            GNDTagDB.INDICATOR_111_D, GNDTagDB.INDICATOR_111_C);
    }

    private String format100(Line line) {
        String p = formatSubfields(line.getSubfields());
        Line line548 = GNDUtils.getDatlLine(actualRecord);
        String d = "";
        if (line548 != null) {
            d = ", " + format548(line548);
        }
        return p + d;
    }

    private String format151(Line line) {
        return exceptionHandling(line, GNDTagDB.INDICATOR_151_Z,
            GNDTagDB.DOLLAR_G);
    }

    private String format548(Line line) {
        List<Subfield> subs = line.getSubfields();
        Pair<List<Subfield>, List<Subfield>> lists =
            FilterUtils.divide(
                subs,
                isInIndicatorList(GNDTagDB.INDICATOR_548_A,
                    GNDTagDB.INDICATOR_548_B));
        List<Subfield> ab = lists.first;
        if (ab.isEmpty())
            return formatSubfields(lists.second);

        String span = "-";
        for (Subfield subfield : ab) {
            if (subfield.getIndicator() == GNDTagDB.INDICATOR_548_A)
                span = subfield.getContent() + "-";
            else
                span += subfield.getContent();
        }
        return span;
    }

    public String formatSubfields(Collection<Subfield> subfields) {
        String content = "";
        for (Subfield subfield : subfields) {
            content += formatStandard(subfield);
        }
        return content;
    }

    /**
     * Standardverhalten. Unterfelder, die nicht in getPreAndPost() erfasst
     * sind, also mit null erfasst, führen zu einem Leerstring.
     * 
     * @param pair
     * @param subfield
     * @return			prä + content + post / ""
     */
    public final String formatStandard(final Subfield subfield) {
        Indicator indicator = subfield.getIndicator();
        Pair<String, String> pair = getPreAndPost(indicator);
        if (pair == null)
            return "";
        else {
            String content = getContentWithoutBrackets(subfield);
            return pair.first + content + pair.second;
        }
    }

    /**
     * Sonderbehandlung für Klammern (im Augenblick abgeschaltet).
     * 
     * @param subfield
     * @param indicator
     * @return
     */
    public String getContentWithoutBrackets(final Subfield subfield) {
        String content = subfield.getContent();
        Indicator indicator = subfield.getIndicator();
        if (REMOVE_BRACKETS)
            if (indicator == GNDTagDB.DOLLAR_G) {
                content = content.replace(" (", ", ");
                content = content.replace(")", "");
            }
        return content;
    }

    IPredicate<Subfield> isInIndicatorList(final Indicator... indicators) {
        final Set<Indicator> set = new HashSet<>(Arrays.asList(indicators));
        return new IPredicate<Subfield>() {
            @Override
            public boolean accept(Subfield subfield) {
                return set.contains(subfield.getIndicator());
            }
        };
    }

    final static IFunction<Subfield, String> SUB_TO_CONTENT =
        new IFSubfieldToContent();

    Collection<String> getContents(Collection<Subfield> subfields) {
        return FilterUtils.map(subfields, SUB_TO_CONTENT);
    }

    /**
     * 
     * @param record    nicht null
     * @return          RDA-Ansetzung mit Deskriptionszeichen
     * @throws IllFormattedLineException
     *                  wenn Datensatz nicht korrekt formatiert ist 
     */
    public static String getRDAHeading(final Record record)
        throws IllFormattedLineException {
        try {
            RangeCheckUtils.assertReferenceParamNotNull("record", record);
            Line line = GNDUtils.getHeading(record);
            RDAFormatter rdaFormatter = new RDAFormatter(record);
            return rdaFormatter.format(line);
        } catch (Exception e) {
            throw new IllFormattedLineException(e.getMessage());
        }
    }

    public static void main(String[] args) throws IllFormattedLineException {
        Record record = RecordUtils.readFromClip();
        System.out.println(getRDAHeading(record));
    }

}
